前幾天也有介紹過,透過 UseCase 將資料交換的相關邏輯抽出來,既可以讓 ViewModel 變得更簡潔,同時一個個的 UseCase 也能夠直接進行測試,這樣處理 UseCase 及 ViewModel 的測試就不會太複雜了,下面開始進行今天的主題。
因為邏輯拆分到 UseCase 後已經變得很簡單了,所以測試起來沒有特別困難的地方,我對 UseCase 的測試思路是塞入各種不同的資料來檢查最後回傳的 Result
結果是否正確。
以 GetTasksUseCase
為例,可以透過輸入各種不同的 filter type 來檢查 Result
的結果。或是看看沒有資料時的狀態,或者幹脆看看程式碼 crash 時有沒有正確處理。
這邊沒有太多值得細講的內容,所以直接在這裡附上 完成的程式碼 供大家參考。
在進行 ViewModel 的測試時幾乎與前面差不多,除了會遇到一個情況:怎麼測試 LiveData
?
在聊這個問題之前,需要知道一件事情:
在使用 LiveData 時常常會搭配 LiveData 的 Transformations API 進行一些操作,方便做一些資料的變換或是一些處理等。但這些使用了 Transformations.map()
等方法的 LiveData 必須要被 觀察 (observe) 才能得到資料的變化。
因此在實務操作時:
除了
MutableLiveData
之外,很多時候使用 LiveData 都需要 observe
MutableLiveData
除外的原因是因為可以直接使用 MutableLiveData.value
獲得 LiveData 的資料,但這麼做一旦哪天在程式碼裡把 MutableLiveData 改成其他類型的 LiveData 時很有可能測試就壞掉了,所以保險起見我通常會建議別人測試 LiveData 還是乖乖 observe 比較好。
關於這個問題 Google issue tracker 也有人反應了,但不知道何時才會有一個官方的解決方案。不過我們可以自己寫一個簡單的 Util 來處理這個問題:
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeout: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(t: T) {
data = t
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
afterObserve.invoke()
if (!latch.await(time, timeout)) {
this.removeObserver(observer)
throw TimeoutException("LiveData value was never set")
}
@Suppress("UNCHECKED_CAST")
return data as T
}
......
@Test
fun testSample() {
......
assertEquals(viewModel.liveData1.getOrAwaitValue(), "foo")
}
具體的思路是寫一個 observeForever
方法,在 observe 後移除掉 observer ,並且為了避免程式無回應,在等待一段時間後會強行停止。
當然使用時也要記得加上 InstantTaskExecutorRule
以確保不會有線程上的問題。
解決了 LiveData 的問題後,TasksViewModel 的測試就可以繼續進行了,最後附上完成的 程式碼 給大家參考。